14. 리팩터링: 기존의 코드를 성장시키는 기술

📌 Contents

📌 리팩터링의 흐름

  • 리팩터링: 실질적인 동작은 유지하면서, 구조만 정리하는 작업

리팩터링으로 코드를 변경하는 일련의 흐름

  1. 중첩을 제거하여 보기 좋게 만들기

    • 조건 판정을 위해 if 조건문을 여러 번 중첩하고 있다면,
    • 조기 리턴을 활용하여 조건을 반전해서 if 조건문의 중첩을 제거하기
  2. 의미 있는 단위로 로직 정리하기

    • 조건 확인과 값 대입이 뒤섞여 있으면, 로직이 정리되지 않음
    • 조건 확인과 값 대입 로직을 각각 분리해서 정리하자
    • 조건 확인을 모두 완료한 이후에 값을 대입하면 로직의 가독성이 좋아짐
  3. 조건을 읽기 쉽게 하기

    • 논리 부정 연산자 "!"를 사용하면 코드를 읽을 때 한 번 더 생각해야 해서 가독성이 떨어짐
      • 유효하지 않을 때 if(!isEnabled)를 if(isDisabled)로 바꾸기
  4. 무턱대고 작성한 로직을 목적을 나타내는 메서드로 바꾸기

    • 무턱대고 로직을 작성하면, 그 로직 부분만 보았을 때 목적을 알기 힘듬
    • 목적을 나타내는 메서드로 만들어 사용하는 것이 좋음


  • 실제 프로덕션 코드는 훨씬 복잡하므로, 리팩터링 난이도가 굉장히 높음
  • 아무리 주의를 기울이더라도, 인간의 주의력에는 한계가 있어, 실수로 인해 동작에 변화가 생기면, 버그가 발생할 수 있음
  • 어떻게 해야 안전하게 리팩터링할 수 있을까?

📌 단위 테스트로 리팩터링 중 실수 방지하기

  • 가장 확실하게 실수를 줄일 수 있는 방법은 단위 테스트임
  • 단위 테스트는 작은 기능 단위로 동작을 검증하는 테스트를 의미
  • 일반적으로는 '테스트 프레임워크와 테스트 코드를 활용해서 메서드 단위로 동작을 검증하는 방법'이라고 생각해도 됨
  • 리팩터링과 단위 테스트는 항상 세트로 이야기 됨
  • 일반적으로 악마를 불러들이는 나쁜 코드에는 테스트 코드가 작성되어 있지 않은 경우가 많음
  • 테스트가 없는 프로덕션 코드가 있을때, 테스트 코드를 작성하고 리팩터링하는 과정을 살펴보자

테스트 코드를 사용한 리팩터링 흐름

  1. 이상적인 구조의 클래스 기본 형태를 어느 정도 잡기

    • 첫 번째로 이상적인 구조의 클래스 기본 형태를 어느 정도 잡으면, 이 단계에서는 클래스가 기본적인 사양을 만족하지 못함
    • 이제 이러한 미완성된 클래스를 대상으로 하는 테스트를 작성하고, 기존 클래스이 로직을 차근차근 옮겨서 완성된 클래스로 만듦
  2. 이 기본 형태를 기반으로 테스트 코드 작성하기

  3. 테스트 실패시키기

    • 기대한 대로 실패 혹은 성공하지 않는다면 테스트 코드나 프로덕션 코드중 어딘가에 오류가 있다는 방증이기 떄문에,
    • 단위 테스트는 프로덕션 코드를 구현하기 전에, 실패와 성공을 확인해야 함
    • 현재 단계에서 테스트를 실행하면 둘 다 실패할 것임
  4. 테스트 성공을 위한 최소한의 코드 작성해서 테스트 성공시키기

  5. 기본 형태의 클래스 내부에서 리팩터링 대상 코드를 호출하기

  6. 테스트가 성공할 수 있도록, 조금씩 로직을 이상적인 구조로 리팩터링하기

  7. 리팩터링 중간에 실수로 로직을 잘못 작성하면, 테스트가 실패하므로 곧바로 알아차릴 수 있음

  8. 따라서 안전하게 로직을 변경할 수 있음

📌 불확실한 사양을 이해하기 위한 분석 방법

  • 단위 테스트를 사용한 리팩터링은 처음부터 사양을 알고 있다는 전제가 있기에 테스트를 작성할 수 있음
  • 사양을 제대로 모른다면, 리팩터링을 위한 테스트 코드를 작성할 수 없음
  • 이럴 때는 어떻게 해야 할까?

사양 분석 방법 1: 문서화 테스트

  • 분석하고 싶은 메서드의 테스트를 작성해서, 해당 메서드가 어떤 동작을 하는지 확인하는 방법

사양 분석 방법 2: 스크래치 리팩터링

  • 로직의 의미와 구조를 분석하기 위해 시험 삼아 리팩터링하는 것
  • 대상 코드를 리포지터리에서 체크아웃
  • 따로 테스트 코드 작성 없이 리팩터링
  • 코드가 정리되어 가독성이 좋아지면 장점이 생김
    • 로직의 사양을 이해할 수 있게 됨
    • 이상적인 구조가 보임
    • 쓸데없는 코드(데드 코드)가 보임
    • 테스트 코드를 어떻게 작성해야 할지 보임
  • 스크래치 리팩터링으로 분석한 결과를 기반으로 이상적인 구조를 떠올리고, 테스트 코드를 작성하면서 정식으로 리팩터링 하면 됨
  • 스크래치 리팩터링은 어디까지나 분석용이므로, 리포지터리에 병합해서는 안됨
  • 역할을 마쳤다면 그냥 파기

📌 IDE의 리팩터링 기능

  • Intellij IDEA 리팩터링 기능 2가지 소개

리네임(이름 변경)

  • 한 번에 클래스, 메서드, 변수의 이름을 전부 변경하는 리팩터링 기능

메서드 추출

  • 로직 일부를 메서드로 추출해 주는 기능

📌 리팩터링 시 주의 사항

기능 추가와 리팩터링 동시에 하지 않기

  • 기능 추가와 리팩터링을 동시에 하지 말고, 둘 중 하나에 집중하자
  • 이러한 전환을 '두 개의 모자'라고 표현함
  • 작업할 때 '기능 추가 모자'와 '리팩터링 모자' 중에서 하나만 쓰고 있어야 함
  • 같이 하면 내가 지금 기능 추가중인지 리팩터링 중인지 구분하기 힘듬
  • 또한 버그가 기능 추가때문에 발생했는지 리팩터링 때문에 발생했는지 분석하기 힘듬

작은 단계로 실시하기

  • 리팩터링은 작은 단계로 실시하는 것이 좋음
  • 커밋은 어떻게 리팩터링 했는지 차이를 알 수 있는 단위로 하기
  • 여러 번 커밋 했다면, 풀 리퀘스트를 작성하는 것이 좋음

불필요한 사양은 제거 고려하기

  • 코드에 버그가 있거나 다른 사양과 모순되는 점이 있다면 리팩터링해도 바로잡기 힘듬
  • 불필요한 사양과 코드를 미리 제거할 수 있다면, 더 쾌적하게 리패터링 할 수 있음

❓ Questions

❓ 자바스크립트의 단위 테스트

  • 자바스크립트에서 가장 많이 쓰이는 단위 테스트 라이브러리는 jest와 mocha이다.

jest

  • 주로 리액트에서 많이 쓰인다.
  • jest는 Test Runner, Test Matcher, Test Mock 기능 모두 제공한다.
    • Test Matcher: 단언문, expect/assert
    • Test Mock: 특정 함수나 모듈을 테스트할 때 그 동작을 대체하는 방식
      • jest에서는 mock, spyOn, fn 등이 있고, sinon에는 mock, spy, stub 등의 기능이 있음
  • 따라서 Setup 과정이 간단해서 러닝커브가 낮다.
  • 확장성이 좋지 않다.

mocha

  • 주로 node.js 환경에서 많이 쓰인다.
  • mocha는 Test Runner 기능만 제공한다.
  • Test Matcher, Test Mock 기능은 따로 라이브러리가 필요하다.
    • Test Matcher에는 chai 라이브러리가 많이 사용됨
    • Test Mock에는 sinon 라이브러리가 많이 사용됨
  • 따라서 Setup 과정이 간단해서 러닝커브가 높다.
  • 확장성이 좋다.

vitest

  • 웹 번들러인 vite에서 사용하는 test 도구
  • jest와 비슷함

❓ VSCode의 리팩터링 기능

  • 책에서는 Intellij IDEA에 대한 리팩터링 기능만 나와서 VSCode의 리팩터링 기능에 대해 찾아보았다.


  • 일단 리네임 기능도 존재한다.
  • 이름을 바꾸고 싶은 부분을 선택한 뒤 control + d 또는 command + d를 누르면 같은 부분이 하나씩 선택된다.
  • 선택한 뒤 f12를 누르면 전체가 선택이 된다.
  • 선택한 뒤 마우스 오른쪽 클릭 후에 모든 항목 변경을 눌러도 전체가 선택이 된다.


  • 이 외에도 리팩터링 하고 싶은 부분을 선택 후 마우스 오른쪽 키를 누르면 리팩터링이라는 항목이 있는데 이걸 누르면 여러 리팩터링을 할 수 있다.
  • 책에 나온 메서드 추출과 비슷한 것들도 할 수 있고 여러가지 것들이 있으니 나중에 리팩터링을 할 때 사용해보면 좋을 것 같다.

results matching ""

    No results matching ""